#include "ChartView.h"

namespace
{

constexpr int SHOW_INTERVAL = 1000 * 1 / 10;

}

ChartView::ChartView(Chart *chart)
    : QtCharts::QChartView(chart)
    , _show_timer(startTimer(SHOW_INTERVAL))
    , _is_rubber_band_enabled()
{
    enableRubberBand(_is_rubber_band_enabled);
    setSmoothLines(false);
    connect(
        chart, &Chart::useOpenGLChanged
        , this, &ChartView::useOpenGLChangedHandler
    );
}

ChartView::~ChartView()
{
    if (_show_timer > 0) killTimer(_show_timer);
}

void ChartView::enableRubberBand(bool enabled)
{
    _is_rubber_band_enabled = enabled;
    setRubberBand(
        _is_rubber_band_enabled
        ? QChartView::RectangleRubberBand
        : QChartView::NoRubberBand
    );
}

void ChartView::setSmoothLines(bool is_set)
{
    bool smooth_lines = is_set;
    setRenderHint(QPainter::Antialiasing, smooth_lines);
    setRenderHint(QPainter::SmoothPixmapTransform, smooth_lines);
    qobject_cast<Chart *>(chart())->setUseOpenGL(!smooth_lines);
}

void ChartView::useOpenGLChangedHandler(bool used)
{
    emit smoothLinesChanged(!used);
}

void ChartView::wheelEvent(QWheelEvent *event)
{
    if (!_is_rubber_band_enabled)
    {
        QtCharts::QChartView::wheelEvent(event);
        return;
    }

    if (
        event->modifiers() != Qt::ControlModifier
        && event->modifiers() != Qt::ShiftModifier
        && event->modifiers() != (Qt::ControlModifier | Qt::ShiftModifier)
    )
    {
        QtCharts::QChartView::wheelEvent(event);
        return;
    }

    if (event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier))
    {
        // QWheelEvent: delta > 0 if up-left
        _angleZoomEvent(event->angleDelta());
        return;
    }

    bool inverted = event->modifiers() & Qt::ShiftModifier;

    constexpr auto swap_coords
        = [](QPoint p) -> QPoint { return { p.y(), p.x() }; };

    auto angle_delta = event->angleDelta();
    angle_delta.setX(-angle_delta.x());
    _angleScrollEvent(inverted ? swap_coords(angle_delta) : angle_delta);
}

void ChartView::_angleZoomEvent(QPoint delta)
{
    const auto angle_delta = QPointF(delta) / 8;
    const auto y_angle = angle_delta.y();

    const qreal y_zoom = std::pow(2, y_angle / 180);
    chart()->zoom(y_zoom);
}

void ChartView::_angleScrollEvent(QPoint delta)
{
    const auto angle_delta = QPointF(delta) / 8;
    const auto x_angle = angle_delta.x();
    const auto y_angle = angle_delta.y();

    const auto x_radius = chart()->plotArea().width() / 180;
    const auto y_radius = chart()->plotArea().height() / 180;

    const qreal x_scroll = x_angle * x_radius;
    const qreal y_scroll = y_angle * y_radius;
    chart()->scroll(x_scroll, y_scroll);
}

void ChartView::mousePressEvent(QMouseEvent *event)
{
    if (!_is_rubber_band_enabled)
    {
        QtCharts::QChartView::mousePressEvent(event);
        return;
    }

    // Alternative to left press is fitBounds
    if (
        event->modifiers() == Qt::AltModifier
        && event->buttons() == Qt::LeftButton
    )
    {
        qobject_cast<Chart *>(chart())->fitBounds();
        return;
    }

    QtCharts::QChartView::mousePressEvent(event);
}

void ChartView::mouseReleaseEvent(QMouseEvent *event)
{
    if (!_is_rubber_band_enabled)
    {
        QtCharts::QChartView::mouseReleaseEvent(event);
        return;
    }

    // Alternative to right release (zoomOut) is zoomIn
    if (
        event->modifiers() == Qt::AltModifier
        && event->button() == Qt::RightButton
        && event->buttons() == Qt::NoButton
    )
    {
        chart()->zoomIn();
        return;
    }

    QtCharts::QChartView::mouseReleaseEvent(event);
}

void ChartView::timerEvent(QTimerEvent *event)
{
    if (event->timerId() == _show_timer) 
    {
        auto ch = qobject_cast<Chart *>(chart());
        ch->updateSeries();
        if (!_is_rubber_band_enabled) ch->fitBounds();
        return;
    }

    QObject::timerEvent(event);
}
